import traceback
from contextlib import contextmanager
from datetime import datetime

from loguru import logger
from sqlalchemy import Column, SmallInteger, DateTime, create_engine, Integer
from sqlalchemy.orm import declarative_base, sessionmaker

from configs import config

Base = declarative_base()
__all__ = ['BaseModel', 'db']


class BaseModel(Base):
    __abstract__ = True

    id = Column(Integer, primary_key=True, autoincrement=True)
    create_time = Column(DateTime, nullable=False, default=datetime.now())
    # timestamp = Column(DateTime, nullable=False, default=datetime.now().timestamp())
    update_time = Column(DateTime)
    status = Column(SmallInteger, index=True, comment='-1:已失效0:未发布;1:已发布')

    def columns(self, exclude: list = None):
        fields = self.__table__.columns.keys()
        return fields

    def set_attr(self, data: dict, exclude: list = None):
        a = ['create_time', 'id', 'timestamp']
        if exclude:
            exclude.extend(a)
        else:
            exclude = a
        for k, v in data.items():
            if k not in exclude and hasattr(self, k):
                setattr(self, k, v)
            else:
                continue

    @classmethod
    def get_page_data(cls, page, page_size, query: dict = None):
        items = db.session.query(cls).order_by(cls.id.desc())
        if query:
            items = items.filter(**query)
        count = items.count()
        items = items.offset((page - 1) * page_size).limit(page_size)
        return items, count

    @classmethod
    def get_one(cls, query: dict):
        item = db.session.query(cls).filter(getattr(cls, query['key']) == query['value']).first()
        return item


class Database:

    def __init__(self, driver, user, pwd, host, db, sid=None):
        self.driver = driver
        self.user = user
        self.pwd = pwd
        self.host = host
        self.db = db
        self.sid = sid
        self._init_driver_adapter()

    @contextmanager
    def auto_commit(self, throw=True):
        try:
            yield
            self._db.commit()
        except Exception as e:
            self._db.rollback()
            logger.exception('%r' % e)
            if throw:
                raise e

    def add(self, obj):
        self._db.add(obj)

    @property
    def engine(self):
        return self._engine

    @property
    def session(self):
        return self._db

    def save(self, obj):
        self._db.add(obj)
        self._db.commit()

    def _init_driver_adapter(self):
        try:
            func = f'_init_{self.driver}_engine'
            getattr(self, func)()
        except Exception:
            print(f"init database engine error, msg: {traceback.format_exc()}")

    def _init_oracle_engine(self):
        db_url = f'{self.driver}://{self.user}:{self.pwd}@{self.host}/{self.sid}'
        self._create_database_instance(db_url)

    def _init_mysql_engine(self):
        db_url = f'{self.driver}://{self.user}:{self.pwd}@{self.host}/{self.db}?charset=utf8'
        self._create_database_instance(db_url)

    def _create_database_instance(self, db_url):
        self._engine = create_engine(db_url, echo=True)
        session = sessionmaker(bind=self._engine)
        self._db = session()


db = Database(config.DB_DRIVER, config.DB_USER, config.DB_PWD, config.DB_HOST, config.DB_DB)
BaseModel.metadata.create_all(db.engine)

